<template>
  <div class="timeline-player">
    <div class="controls">
      <px-button class="player-btn-o" link @click="prev"><Rewind /></px-button>
      <px-button v-show="!playing" class="player-btn" link @click="togglePlay"><Play /></px-button>
      <px-button v-show="playing" class="player-btn" link @click="togglePlay"><Pause /></px-button>
      <px-button class="player-btn-o" link @click="next"><Forward /></px-button>
    </div>

    <div class="timeline-container">
      <div class="timeline-bar" :style="{ background: computedTimelineGradient }">
        <div
          v-for="(item, index) in items"
          :key="index"
          class="timeline-segment"
          :style="{
            width: getSegmentWidth(item.duration) + '%',
            borderRight: index < items.length - 1 ? '1px solid white' : 'none'
          }"
          @click="seekToItem(index)"
        >
          <px-tooltip placement="top">
            <template #content>{{ item.name }}</template>
            <div class="segment-area" />
          </px-tooltip>
        </div>

        <div class="progress-indicator" :class="{ loading: isLoading }" :style="{ left: currentPercent + '%' }" />

        <div class="indicator-label" :style="{ left: currentPercent + '%' }">{{ currentItemName }}</div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
import Play from "../common/icons/play.svg?component";
import Pause from "../common/icons/pause.svg?component";
import Forward from "../common/icons/forward.svg?component";
import Rewind from "../common/icons/rewind.svg?component";
const props = defineProps({
  items: {
    type: Array,
    required: true
  },
  preloadThreshold: {
    type: Number,
    default: 3
  },
  loop: {
    type: Boolean,
    default: true
  }
});

const emit = defineEmits(["onPrev", "onNext", "onPlayToggle", "onPreloadNext", "onItemChange", "onItemEnd"]);

const playing = ref(false);
const currentItemIndex = ref(0);
const currentTime = ref(0);
const isLoading = ref(false);
let rafId = null;

const totalDuration = computed(() => props.items.reduce((sum, item) => sum + item.duration, 0));
const currentPercent = computed(() => (currentTime.value / totalDuration.value) * 100);
const currentItemName = computed(() => props.items[currentItemIndex.value]?.name || "");
watch(
  () => props.items,
  val => {
    pause();
    currentItemIndex.value = 0;
    currentTime.value = 0;
  }
);
const computedTimelineGradient = computed(() => {
  const percent = currentPercent.value.toFixed(2);
  return `linear-gradient(to right, #C6E2FF 0%, #C6E2FF ${percent}%, #EBEDF0 ${percent}%, #EBEDF0 100%)`;
});

const getSegmentWidth = duration => (duration / totalDuration.value) * 100;

function play() {
  if (!playing.value && !isLoading.value) {
    playing.value = true;
    emit("onPlayToggle", true);
    loopFun();
  }
}

function pause() {
  if (playing.value) {
    playing.value = false;
    emit("onPlayToggle", false);
    cancelAnimationFrame(rafId);
  }
}

function togglePlay() {
  playing.value ? pause() : play();
}

function prev() {
  const newIndex = Math.max(0, currentItemIndex.value - 1);
  currentItemIndex.value = newIndex;
  emit("onPrev", newIndex);
  emit("onItemChange", newIndex);
  currentTime.value = getTimeBefore(newIndex);
}

function next() {
  let newIndex = currentItemIndex.value + 1;
  if (newIndex >= props.items.length) {
    if (props.loop) {
      newIndex = 0;
    } else {
      pause();
      return;
    }
  }
  currentItemIndex.value = newIndex;
  emit("onNext", newIndex);
  emit("onItemChange", newIndex);
  currentTime.value = getTimeBefore(newIndex);
}

function getTimeBefore(index) {
  return props.items.slice(0, index).reduce((sum, item) => sum + item.duration, 0);
}
let prevTimestamp = null;
const MAX_DELTA = 0.1; // 单帧最多增加 100ms

function loopFun() {
  const tick = timestamp => {
    if (!playing.value || isLoading.value) return;
    if (prevTimestamp === null) {
      prevTimestamp = timestamp;
    }

    const delta = Math.min((timestamp - prevTimestamp) / 1000, MAX_DELTA);

    prevTimestamp = timestamp;
    currentTime.value = +(currentTime.value + delta).toFixed(5);
    const currentItem = props.items[currentItemIndex.value];
    const currentStart = getTimeBefore(currentItemIndex.value);
    const itemElapsed = currentTime.value - currentStart;

    if (currentItem.duration - itemElapsed <= props.preloadThreshold && currentItemIndex.value < props.items.length - 1) {
      emit("onPreloadNext", currentItemIndex.value + 1);
    }

    if (itemElapsed >= currentItem.duration) {
      emit("onItemEnd", currentItemIndex.value);
      isLoading.value = true;
      pause();
      return;
    }

    rafId = requestAnimationFrame(tick);
  };
  rafId = requestAnimationFrame(tick);
}

function seekToItem(index) {
  if (index >= 0 && index < props.items.length) {
    currentItemIndex.value = index;
    currentTime.value = getTimeBefore(index);
    emit("onItemChange", index);
  }
}

function getCurrentItem() {
  return {
    index: currentItemIndex.value,
    ...props.items[currentItemIndex.value]
  };
}

function continueAfterLoading() {
  isLoading.value = false;
  next();
  play();
}

onMounted(() => {});
onBeforeUnmount(() => {
  cancelAnimationFrame(rafId);
});

defineExpose({ play, pause, seekToItem, getCurrentItem, continueAfterLoading });
</script>

<style scoped>
.timeline-player {
  position: absolute;
  bottom: 45px;
  left: 12px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  width: 1000px;
  height: 40px;
  padding: 0 16px;
  background: #fff;
  border-radius: 2px;
}

.controls {
  display: flex;
  width: 60px;
  margin-right: 20px;
}

.player-btn-o {
  width: 20px;
  height: 20px;
  margin-left: 0 !important;
}

.player-btn {
  width: 20px;
  height: 20px;
  margin-right: 5px;
  margin-left: 5px !important;
}

.timeline-container {
  flex: 1;
}

.timeline-bar {
  position: relative;
  height: 20px;

  /* overflow: hidden; */
}

.timeline-segment {
  position: relative;
  display: inline-block;
  float: left;
  height: 100%;
}

.segment-area {
  width: 100%;
  height: 100%;
}

.progress-indicator {
  position: absolute;
  top: 0;
  z-index: 2;
  width: 2px;
  height: 20px;
  background-color: #3b82f6;
  transition: left 0.05s ease-out;
}

.progress-indicator.loading {
  background-color: orange;
  animation: pulse 1s infinite;
}

.indicator-label {
  position: absolute;
  z-index: 3;
  font-size: 12px;
  color: #909399;
  white-space: nowrap;
  pointer-events: none;
  transform: translateX(4px);
}

@keyframes pulse {
  0% {
    opacity: 1;
  }

  50% {
    opacity: 0.5;
  }

  100% {
    opacity: 1;
  }
}
</style>
